/*
 * Segment.java
 *
 * Created on March 27, 2007, 2:56 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.app.db;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import org.atomojo.app.Nil;
import org.restlet.data.MediaType;

/**
 *
 * @author alex
 */
public class Feed extends DBObject implements Modifiable {
   
   int id;
   int parent;
   String name;
   UUID uuid;
   Timestamp created;
   Timestamp modified;
   Timestamp syncd;
   
   /** Creates a new instance of Segment */
   public Feed(DB db,int id,int parent,String name,UUID uuid,Timestamp created,Timestamp modified,Timestamp syncd) {
      super(db);
      this.id = id;
      this.name = name;
      this.uuid = uuid;
      this.parent = parent;
      this.created = created;
      this.modified = modified;
      this.syncd = syncd;
   }

   public int getId() {
      return id;
   }

   public int getParentId() {
      return parent;
   }

   public String getName() {
      return name;
   }

   public UUID getUUID() {
      return uuid;
   }

   public Timestamp getCreated()
   {
      return created;
   }

   public Timestamp getEdited()
   {
      return modified;
   }
   
   public Timestamp getSynchronizedAt()
   {
      return syncd;
   }
   
   public void markSynchronized()
      throws SQLException
   {
      syncd = new Timestamp((new Date()).getTime());
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_FEED_SYNCHRONIZED);
         s.setTimestamp(1,syncd);
         s.setInt(2, id);
         s.executeUpdate();
      } finally {
         db.release(s);
         db.release(dbConnection);
      }
   }
   
   public Timestamp touch()
      throws SQLException
   {
      return touch(new Timestamp((new Date()).getTime()));
   }
   
   Timestamp touch(Timestamp modified)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_FEED_UPDATED);
         s.setTimestamp(1,modified);
         s.setInt(2, id);
         s.executeUpdate();
         this.modified = modified;
         return modified;
      } finally {
         db.release(s);
         db.release(dbConnection);
      }
   }
   
   public Timestamp edited() 
      throws SQLException
   {
      Timestamp t = touch();
      db.getJournal().recordModify(id);
      return t;
   }
   
   public Feed createChild(String name)
      throws SQLException
   {
      return createChild(name,UUID.randomUUID());
   }
   
   public Feed createChild(String name,UUID uuid)
      throws SQLException
   {
      if (uuid==null) {
         uuid = UUID.randomUUID();
      } else {
         if (db.findFeed(uuid)!=null) {
            uuid = UUID.randomUUID();
         }
      }
      synchronized (this) {
         Feed f = getChild(name);
         if (f!=null) {
            return null;
         }
      }
      Timestamp tstamp = new Timestamp((new Date()).getTime());
      PreparedStatement s = null;
      ResultSet r = null;
      DBConnection dbConnection = db.getConnection();
      try {
         s = dbConnection.getStatement(DB.STATEMENT_CREATE_FEED);
         // name
         s.setString(1, name);
         // parent
         if (id<0) {
            s.setNull(2,Types.INTEGER);
         } else {
            s.setInt(2,id);
         }
         // uuid of feed
         s.setString(3,uuid.toString());
         // created
         s.setTimestamp(4,tstamp);
         // modifed
         s.setTimestamp(5,tstamp);
         // synchronized
         s.setTimestamp(6,tstamp);
         int count = s.executeUpdate();
         if (count>0) {
            db.release(s);
            s = dbConnection.getStatement(DB.STATEMENT_LAST_ID_FROM_FEEDS);
            r = s.executeQuery();
            if (r.next()) {
               int childId = r.getInt(1);
               Feed f = new Feed(db,childId,id,name,uuid,tstamp,tstamp,tstamp);
               db.getJournal().recordCreate(childId);
               return f;
            }
         }
         return null;
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
   public Feed getChild(String name)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      ResultSet r = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_FEED_BY_NAME);
         s.setString(1,name);
         s.setInt(2,id);
         r = s.executeQuery();
         if (r.next()) {
            int childId = r.getInt(1);
            Feed f = new Feed(db,childId,id,name,UUID.fromString(r.getString(2)),r.getTimestamp(3),r.getTimestamp(4),r.getTimestamp(5));
            return f;
         } else {
            return null;
         }
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
   public Iterator<Feed> getChildren()
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      try {
         PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_ALL_FEED_CHILDREN);
         s.setInt(1, id);
         return db.iterateFeeds(s.executeQuery(),dbConnection);
      } catch (SQLException ex) {
         db.release(dbConnection);
         throw ex;
      }
   }
   
   public Iterator<Entry> getEntries() 
      throws SQLException
   {
      return getEntries(1,-1);
   }
   
   public Iterator<Entry> getEntries(int start,int max) 
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      try {
         if (start==1 && max<0) {
            PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_ALL_FEED_ENTRIES);
            s.setInt(1,id);
            ResultSet r = s.executeQuery();
            return new DBIterator<Entry>(r,new ResultConstructor<Entry>() {
               public Entry newInstance(ResultSet set) 
                  throws SQLException
               {
                  // TODO: get the whole entry to avoid double query
                  return db.getEntry(set.getInt(1));
               }
            },db,dbConnection);
         } else {
            PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_ALL_FEED_ENTRIES);
            s.setInt(1,id);
            ResultSet r = s.executeQuery();
            while (start>1) {
               r.next();
               start--;
            }
            DBIterator<Entry> iter = new DBIterator<Entry>(r,new ResultConstructor<Entry>() {
               public Entry newInstance(ResultSet set) 
                  throws SQLException
               {
                  // TODO: get the whole entry to avoid double query
                  return db.getEntry(set.getInt(1));
               }
            },db,dbConnection);
            iter.setMaximum(max);
            return iter;
         }
      } catch (SQLException ex) {
         db.release(dbConnection);
         throw ex;
      }
   }
   
   public Entry createEntry()
      throws SQLException
   {
      return createEntry(UUID.randomUUID());
   }
   
   public Entry createEntry(UUID uuid)
      throws SQLException
   {
      Timestamp tstamp = new Timestamp((new Date()).getTime());
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      ResultSet r = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_CREATE_ENTRY);
         // parent feed
         s.setInt(1,id);
         // entry uuid
         s.setString(2,uuid.toString());
         // created
         s.setTimestamp(3,tstamp);
         // modifed
         s.setTimestamp(4,tstamp);
         int count= s.executeUpdate();
         if (count>0) {
            db.release(s);
            s = dbConnection.getStatement(DB.STATEMENT_LAST_ID_FROM_ENTRIES);
            r = s.executeQuery();
            if (r.next()) {
               int entryId = r.getInt(1);
               Entry e = new Entry(db,entryId,id,uuid,tstamp,tstamp);
               touch(tstamp);
               db.getJournal().recordCreate(id,entryId);
               return e;
            }
         }
         return null;
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
   public void delete() 
      throws SQLException
   {
      db.getJournal().recordDelete(this);
      db.deleteFeed(id);
   }
   
   public Feed getParent() 
      throws SQLException
   {
      if (parent<0) {
         return null;
      }
      return db.getFeed(parent);
   }
   
   public String getPath()
      throws SQLException
   {
      List<Feed> ancestors = new ArrayList<Feed>();
      Feed current = this;
      while (current!=null) {
         ancestors.add(current);
         current = current.getParent();
      }
      Collections.reverse(ancestors);
      StringBuilder builder = new StringBuilder();
      for (Feed a : ancestors) {
         builder.append(a.getName());
         builder.append('/');
      }
      String path = builder.toString();
      if (path.charAt(0)=='/') {
         path = path.substring(1);
      }
      return path;
   }
   
   public EntryMedia findEntryResource(String name)
      throws SQLException
   {
      PreparedStatement s = null;
      ResultSet r = null;
      DBConnection dbConnection = db.getConnection();
      try {
         s = dbConnection.getStatement(DB.STATEMENT_RESOURCE_BY_NAME);
         s.setString(1,name);
         s.setInt(2,id);
         r = s.executeQuery();
         EntryMedia resource = null;
         if (r.next()) {
            resource = new EntryMedia(db,r.getInt(1),id,r.getInt(2),name,MediaType.valueOf(r.getString(3)),r.getTimestamp(4),r.getTimestamp(5));
         }
         return resource;
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
   }
   
   public Entry findEntry(UUID uuid)
      throws SQLException
   {
      PreparedStatement s = null;
      ResultSet r = null;
      DBConnection dbConnection = db.getConnection();
      try {
         s = dbConnection.getStatement(DB.STATEMENT_ENTRY_BY_UUID);
         s.setString(1,uuid.toString());
         s.setInt(2,id);
         r = s.executeQuery();
         Entry entry = null;
         if (r.next()) {
            entry = new Entry(db,r.getInt(1),id,uuid,r.getTimestamp(2),r.getTimestamp(3));
         }
         return entry;
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
   }
   
   public void categorize(Term term)
      throws SQLException
   {
      categorize(term,null);
   }
   
   public void categorize(Term term,Object value)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_CATEGORIZE_FEED);
         s.setInt(1, id);
         s.setInt(2,term.getId());
         if (value==null || value==Nil.getInstance()) {
            s.setNull(3, Types.VARCHAR);
         } else {
            s.setString(3, value.toString());
         }
         int count = s.executeUpdate();
      } finally {
         db.release(s);
         db.release(dbConnection);
      }
   }
   
   public void uncategorize(Term term)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_UNCATEGORIZE_FEED);
         s.setInt(1,id);
         s.setInt(2,term.getId());
         int count = s.executeUpdate();
      } finally {
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
   public void uncategorize()
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_DELETE_FEED_CATEGORIZATION);
         s.setInt(1, id);
         s.executeUpdate();
      } finally {
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
   public Iterator<TermInstance<Feed>> getTerms()
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      try {
         PreparedStatement s = dbConnection.getStatement(DB.STATEMENT_FEED_CATEGORIZATION);
         s.setInt(1,id);
         return new DBIterator<TermInstance<Feed>>(s.executeQuery(),new ResultConstructor<TermInstance<Feed>>() {
            public TermInstance<Feed> newInstance(ResultSet set) 
               throws SQLException
            {
               int id = set.getInt(1);
               int term = set.getInt(2);
               String value = set.getString(3);
               return new TermInstance<Feed>(id,db.getTerm(term),Feed.this,value);
            }
         },db,dbConnection);
      } catch (SQLException ex) {
         db.release(dbConnection);
         throw ex;
      }
   }
   
   public TermInstance<Feed> getTerm(Term term)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      ResultSet r = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_FEED_TERM_INSTANCE);
         s.setInt(1, id);
         s.setInt(2,term.getId());
         r = s.executeQuery();
         if (r.next()) {
            int id = r.getInt(1);
            String value = r.getString(2);
            return new TermInstance<Feed>(id,term,Feed.this,value);
         } else {
            return null;
         }
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
   public boolean hasTerm(Term term,String value)
      throws SQLException
   {
      DBConnection dbConnection = db.getConnection();
      PreparedStatement s = null;
      ResultSet r = null;
      try {
         s = dbConnection.getStatement(DB.STATEMENT_FEED_HAS_TERM_INSTANCE);
         s.setInt(1, id);
         s.setInt(2,term.getId());
         s.setString(3,value);
         r = s.executeQuery();
         return r.next();
      } finally {
         db.release(r);
         db.release(s);
         db.release(dbConnection);
      }
      
   }
   
}
